Guía de estilo para contratos inteligentes
Convenciones de nomenclatura
1. Visión arquitectónica
Nuestro protocolo utiliza una arquitectura modular de diamante/faceta, donde cada faceta encapsula un dominio específico (p. ej., intercambios Uniswap, creación de gardens, control de acceso). Las facetas exponen interfaces externas, delegan la lógica a contratos base internos y usan bibliotecas de almacenamiento para la gestión del estado. Este patrón permite la posibilidad de actualizaciones (upgradability), separación de responsabilidades y límites claros entre las funcionalidades del protocolo.
Ejemplo: Faceta Uniswap
- Faceta externa:
UniswapFacet
expone funciones de swap y TWAP. - Interfaz:
IUniswap
define la API externa y los structs de parámetros. - Base interna:
UniswapBase
implementa la lógica central, sólo invocable por la faceta. - Biblioteca de almacenamiento:
UniswapStorage
gestiona el estado persistente. - Biblioteca de errores:
IUniswapBase
define errores y eventos.
2. Convenciones de nomenclatura
Contratos y Facetas
- Contratos de faceta: Sufijo
Facet
(p. ej.,UniswapFacet
). - Contratos base: Sufijo
Base
(p. ej.,UniswapBase
). - Bibliotecas de almacenamiento: Sufijo
Storage
(p. ej.,UniswapStorage
). - Interfaces: Prefijo
I
(p. ej.,IUniswap
). - Interfaces base: Prefijo
I
y sufijoBase
(p. ej.,IUniswapBase
).
Funciones
- Funciones externas: Usar nombres descriptivos en camelCase (p. ej.,
swapExactInputSingleHop
). - Funciones internas: Prefijar con _ (p. ej.,
_swapExactInputSingleHop
). - Inicialización: Sufijo _init (p. ej.,
UniswapFacet_init
).
Structs
- Structs de parámetros: Sufijo
Params
(p. ej.,GardenSwapParams
). - Tipos compuestos: Usar nombres descriptivos (p. ej.,
TokenWithFee
).
Errores
- Prefijo de dominio: Prefijar con el nombre de la faceta (p. ej.,
UniswapFacet_InsufficientBalance
). - Descriptivos: Usar PascalCase, describir el fallo (p. ej.,
UniswapFacet_SwapDeadlineHasPassed
).
Eventos
- Tiempo pasado: Usar pasado para cambios de estado (p. ej.,
UniswapFacetTokensSwapped
). - Prefijo de dominio: Prefijar con el nombre de la faceta para mayor claridad.
Variables
- Variables de estado: Privadas, con prefijo _ en contratos base, almacenadas en bibliotecas de almacenamiento.
- Variables dentro de structs: Usar camelCase, descriptivas.
Carpetas
- Facetas: Cada faceta debe tener una carpeta separada.
- Descriptivas: Usar camelCase (p. ej.,
liquidityPoolRegistry
).
Cabecera de copyright
/*###############################################################################
@title Diamond
@author BLOK Capital DAO
▗▄▄▖ ▗▖ ▗▄▖ ▗▖ ▗▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▄▖ ▗▖ ▗▄▄▄ ▗▄▖ ▗▄▖
▐▌ ▐▌▐▌ ▐▌ ▐▌▐▌▗▞▘ ▐▌ ▐▌ ▐▌▐▌ ▐▌ █ █ ▐▌ ▐▌▐▌ ▐▌ █▐▌ ▐▌▐▌ ▐▌
▐▛▀▚▖▐▌ ▐▌ ▐▌▐▛▚▖ ▐▌ ▐▛▀▜▌▐▛▀▘ █ █ ▐▛▀▜▌▐▌ ▐▌ █▐▛▀▜▌▐▌ ▐▌
▐▙▄▞▘▐▙▄▄▖▝▚▄▞▘▐▌ ▐▌ ▝▚▄▄▖▐▌ ▐▌▐▌ ▗▄█▄▖ █ ▐▌ ▐▌▐▙▄▄▖ ▐▙▄▄▀▐▌ ▐▌▝▚▄▞▘
################################################################################*/
Orden de disposición de los elementos del contrato inteligente
Los elementos del contrato deben disponerse en el siguiente orden:
- Sentencias
pragma
- Sentencias
import
- Eventos
- Errores
- Interfaces
- Bibliotecas (
libraries
) - Contratos
Dentro de cada contrato, biblioteca o interfaz, use el siguiente orden:
- Declaraciones de tipos (
type declarations
) - Variables de estado
- Eventos
- Errores
- Modificadores
- Funciones
Siga la guía oficial de Solidity para más detalles: link
Orden de las funciones
El orden ayuda a los lectores a identificar qué funciones pueden llamar y a encontrar el constructor y las definiciones receive
/ fallback
más fácilmente.
Las funciones deben agruparse según su visibilidad y ordenarse:
- constructor
- función
receive
(si existe) - función
fallback
(si existe) - external
- public
- internal
- private
Dentro de un mismo grupo, colocar las funciones view
y pure
al final.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract A {
constructor() {
// ...
}
receive() external payable {
// ...
}
fallback() external {
// ...
}
// External functions
// ...
// External functions that are view
// ...
// External functions that are pure
// ...
// Public functions
// ...
// Internal functions
// ...
// Private functions
// ...
}
Contrato de ejemplo
El siguiente bloque mantiene el código tal cual; los comentarios de documentación en inglés se pueden traducir si lo desea, pero se mantiene la sintaxis original para garantizar compatibilidad.
// SPDX-License-Identifier: GPL-3.0
/*###############################################################################
@title Diamond
@author BLOK Capital DAO
▗▄▄▖ ▗▖ ▗▄▖ ▗▖ ▗▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▄▖ ▗▖ ▗▄▄▄ ▗▄▖ ▗▄▖
▐▌ ▐▌▐▌ ▐▌ ▐▌▐▌▗▞▘ ▐▌ ▐▌ ▐▌▐▌ ▐▌ █ █ ▐▌ ▐▌▐▌ ▐▌ █▐▌ ▐▌▐▌ ▐▌
▐▛▀▚▖▐▌ ▐▌ ▐▌▐▛▚▖ ▐▌ ▐▛▀▜▌▐▛▀▘ █ █ ▐▛▀▜▌▐▌ ▐▌ █▐▛▀▜▌▐▌ ▐▌
▐▙▄▞▘▐▙▄▄▖▝▚▄▞▘▐▌ ▐▌ ▝▚▄▄▖▐▌ ▐▌▐▌ ▗▄█▄▖ █ ▐▌ ▐▌▐▙▄▄▖ ▐▙▄▄▀▐▌ ▐▌▝▚▄▞▘
################################################################################*/
pragma solidity ^0.8.24;
/*//////////////////////////////////////////////////////////////
IMPORTS
//////////////////////////////////////////////////////////////*/
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/*//////////////////////////////////////////////////////////////
CONTRACTS
//////////////////////////////////////////////////////////////*/
/// @title ProtocolTemplate
/// @notice A starting point for BLOK Capital protocol modules adhering to the style guide.
/// @dev Replace placeholders with domain-specific logic. Follow visibility and layout ordering strictly.
contract ProtocolTemplate {
/*//////////////////////////////////////////////
1) TYPE DECLARATIONS
//////////////////////////////////////////////*/
/// @notice Example struct for cohesive configuration/state.
struct Config {
// slot 0
address admin; // privileged controller (e.g., DAO multisig or Diamond facet admin)
// slot 1
uint96 feeBps; // example fee in basis points
bool paused; // simple circuit breaker
}
/*//////////////////////////////////////////////
2) STATE VARIABLES
//////////////////////////////////////////////*/
/// @notice Packed configuration/state.
Config private _cfg;
/// @notice Generic key-value parameters (optional pattern).
mapping(bytes32 => uint256) private _params;
/// @notice Example immutable version tag for off-chain tooling.
string public constant VERSION = "v0.1.0-template";
/*//////////////////////////////////////////////
3) EVENTS
//////////////////////////////////////////////*/
/// @notice Emitted when the admin address is updated.
/// @param oldAdmin The previous admin.
/// @param newAdmin The new admin.
event AdminUpdated(address indexed oldAdmin, address indexed newAdmin);
/*//////////////////////////////////////////////
4) ERRORS
//////////////////////////////////////////////*/
/// @notice Thrown when a caller that is not the admin invokes an admin-only function.
error NotAdmin();
/// @notice Thrown when an operation uses a zero address where it is not allowed.
error ZeroAddress();
/*//////////////////////////////////////////////
5) MODIFIERS
//////////////////////////////////////////////*/
/// @notice Restricts function to the current admin.
modifier onlyAdmin() {
if (msg.sender != _cfg.admin) revert NotAdmin();
_;
}
/// @notice Reverts when the contract is paused.
modifier whenNotPaused() {
if (_cfg.paused) revert InvalidParameter(LibKeys.K_PAUSED, 1);
_;
}
/*//////////////////////////////////////////////
6) FUNCTIONS
//////////////////////////////////////////////*/
//------------- Constructor -------------
/// @notice Initializes the template with an admin and optional initial params.
/// @param initialAdmin The admin address (DAO, multisig, or Diamond owner).
/// @param initialFeeBps Initial fee basis points (0–10_000).
/// @param initiallyPaused Whether the module starts paused.
constructor(address initialAdmin, uint96 initialFeeBps, bool initiallyPaused) {
if (initialAdmin == address(0)) revert ZeroAddress();
if (initialFeeBps > 10_000) revert InvalidParameter(LibKeys.K_FEE_BPS, initialFeeBps);
_cfg.admin = initialAdmin;
_cfg.feeBps = initialFeeBps;
_cfg.paused = initiallyPaused;
_params[LibKeys.K_FEE_BPS] = initialFeeBps;
_params[LibKeys.K_PAUSED] = initiallyPaused ? 1 : 0;
}
//------------- Receive (if exists) -------------
/// @notice Accepts ETH transfers.
receive() external payable {
// Intentionally empty. Consider emitting an event if tracking deposits is required.
}
//------------- Fallback (if exists) -------------
/// @notice Fallback for non-matching function selectors.
fallback() external payable {
// Intentionally empty. Consider delegating or reverting depending on module design.
}
//------------- External -------------
/// @notice Update the admin address.
/// @param newAdmin The new admin address.
/// @dev External to keep the interface surface clear; restricted via onlyAdmin.
function setAdmin(address newAdmin) external onlyAdmin {
if (newAdmin == address(0)) revert ZeroAddress();
address old = _cfg.admin;
_cfg.admin = newAdmin;
emit AdminUpdated(old, newAdmin);
}
/// @notice Update a numeric parameter by key.
/// @param key The parameter key (see LibKeys).
/// @param value The new value.
/// @dev Example: setting FEE_BPS or PAUSED.
function setParam(bytes32 key, uint256 value) external onlyAdmin {
uint256 old = _params[key];
// Example validation: enforce bounds for known keys.
if (key == LibKeys.K_FEE_BPS) {
if (value > 10_000) revert InvalidParameter(key, value);
_cfg.feeBps = uint96(value);
} else if (key == LibKeys.K_PAUSED) {
_cfg.paused = (value != 0);
}
_params[key] = value;
emit ParamUpdated(key, old, value);
}
//------------- External view -------------
/// @inheritdoc IProtocolTemplate
function admin() external view returns (address) {
return _cfg.admin;
}
/// @inheritdoc IProtocolTemplate
function getParam(bytes32 key) external view returns (uint256 value) {
return _params[key];
}
/// @notice Get the current fee in basis points.
function feeBps() external view returns (uint96) {
return _cfg.feeBps;
}
/// @notice Return whether the contract is paused.
function paused() external view returns (bool) {
return _cfg.paused;
}
//------------- Public -------------
/// @notice Example public function demonstrating the `whenNotPaused` guard.
/// @dev Replace with real business logic (e.g., deposit/withdraw/swap).
function exampleAction(uint256 amount) public whenNotPaused {
// Implement domain-specific logic. This is a stub.
// e.g., _takeFee(amount); _doSomething(amount);
amount; // silence compiler warning for template
}
//------------- Internal -------------
/// @notice Example internal helper to compute a fee.
/// @param amount The gross amount.
/// @return fee The computed fee based on current feeBps.
function _computeFee(uint256 amount) internal view returns (uint256 fee) {
unchecked {
fee = (amount * _cfg.feeBps) / 10_000;
}
}
//------------- Private -------------
// Add private helpers as needed, placed after internal functions.
}
Plantilla de contrato
La plantilla siguiente se mantiene en su forma original de código. Si desea, puedo traducir los comentarios
/// @notice
y otros comentarios en el código al español también.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;
/*###############################################################################
@title Enter contract title
@author BLOK Capital DAO
@notice Enter contract utility
▗▄▄▖ ▗▖ ▗▄▖ ▗▖ ▗▖ ▗▄▄▖ ▗▄▖ ▗▄▄▖▗▄▄▄▖▗▄▄▄▖▗▄▖ ▗▖ ▗▄▄▄ ▗▄▖ ▗▄▖
▐▌ ▐▌▐▌ ▐▌ ▐▌▐▌▗▞▘ ▐▌ ▐▌ ▐▌▐▌ ▐▌ █ █ ▐▌ ▐▌▐▌ ▐▌ █▐▌ ▐▌▐▌ ▐▌
▐▛▀▚▖▐▌ ▐▌ ▐▌▐▛▚▖ ▐▌ ▐▛▀▜▌▐▛▀▘ █ █ ▐▛▀▜▌▐▌ ▐▌ █▐▛▀▜▌▐▌ ▐▌
▐▙▄▞▘▐▙▄▄▖▝▚▄▞▘▐▌ ▐▌ ▝▚▄▄▖▐▌ ▐▌▐▌ ▗▄█▄▖ █ ▐▌ ▐▌▐▙▄▄▖ ▐▙▄▄▀▐▌ ▐▌▝▚▄▞▘
################################################################################*/
/*//////////////////////////////////////////////////////////////
IMPORTS
//////////////////////////////////////////////////////////////*/
/*//////////////////////////////////////////////////////////////
CONTRACTS
//////////////////////////////////////////////////////////////*/
contract ProtocolTemplate {
/*//////////////////////////////////////////////
1) TYPE DECLARATIONS
//////////////////////////////////////////////*/
/*//////////////////////////////////////////////
2) STATE VARIABLES
//////////////////////////////////////////////*/
/// constant
/// public
/// private
/// mapping
/*//////////////////////////////////////////////
3) EVENTS
//////////////////////////////////////////////*/
/*//////////////////////////////////////////////
4) ERRORS
//////////////////////////////////////////////*/
/*//////////////////////////////////////////////
5) MODIFIERS
//////////////////////////////////////////////*/
/*//////////////////////////////////////////////
6) FUNCTIONS
//////////////////////////////////////////////*/
//------------- Constructor -------------
//------------- Receive (if exists) -------------
//------------- Fallback (if exists) -------------
//------------- External -------------
//------------- External view -------------
//------------- Public -------------
//------------- Internal -------------
//------------- Private -------------
// Add private helpers as needed, placed after internal functions.
}